﻿using BlueWave.Interop.Asio;
using SoundEngine3;
using SoundEngine3.AudioIO;
using SoundEngine3.AudioProcessing;
using SoundEngine3.DspPlugins.Meta;
using System;
using System.Collections.Generic;
using System.Threading;

namespace Zquence_Studio_3_WindowsDesktop.SoundEngine
{
    public class AsioNetSoundEngineAdapter : BaseAudioDriver
    {
        private AsioDriver m_currentDriver;
        private int m_last_buffer_size;
        private int m_last_min_buffer_size;
        private int m_last_max_buffer_size;
        private int m_last_input_channels;
        private int m_last_output_channels;
        private double m_last_sampleRate;
        private bool mStarted;

        public override int MinimumBufferSize
        {
            get { return m_last_min_buffer_size; }
        }

        public override int MaximumBufferSize
        {
            get { return m_last_max_buffer_size; }
        }

        public override int PreferredBufferSize
        {
            get { return m_last_buffer_size; }
        }

        public override double SampleRate
        {
            get { return m_currentDriver.SampleRate; }
        }

        public override int InputChannels
        {
            get { return m_currentDriver.NumberInputChannels; }
        }

        public override int OutputChannels
        {
            get { return m_currentDriver.NumberOutputChannels; }
        }

        public override bool Started
        {
            get
            {
                return mStarted;
            }
        }

        public AsioNetSoundEngineAdapter()
            : base("Asio")
        {
            InstalledDriver[] installedDrivers = null;

            // Try to get installed drivers. If exception, problably no asio driver installed.
            try
            {
                installedDrivers = AsioDriver.InstalledDrivers;
            }
            catch (Exception)
            {
                installedDrivers = new InstalledDriver[0];
            }

            foreach (var driver in installedDrivers)
            {
                SoundDevice device = new SoundDevice(driver.Name, this, driver);
                AddSoundDevice(device);
            }
        }

        public override void SetSoundDevice(SoundDevice soundDevice)
        {
            if (m_currentDriver != null)
            {
                // Reset these otherwise it think it can be unchanged if changed sound drivers between ASIO and
                // example DirectSound ect.
                m_last_buffer_size = 0;
                m_last_min_buffer_size = 0;
                m_last_max_buffer_size = 0;
                m_last_input_channels = 0;
                m_last_output_channels = 0;

                m_currentDriver.Stop();

                m_currentDriver.BufferUpdate -= driver_BufferUpdate;
                Thread.Sleep(100);
                m_currentDriver.DisposeBuffers();
                Thread.Sleep(100);
                m_currentDriver.Release();
                Thread.Sleep(100);
                m_currentDriver = null;
                Thread.Sleep(100);

                // TODO: Kraschar här ofta. Fixa!
                //       Verkar ha någonting med ASIO att göra när man stänger ner ZS3.
                GC.Collect();
            }

            if (soundDevice != null)
            {
                try
                {
                    m_currentDriver = AsioDriver.SelectDriver(soundDevice.GetDeviceIdentifier<InstalledDriver>());
                    m_currentDriver.CreateBuffers(false);
                    m_currentDriver.BufferUpdate += driver_BufferUpdate;

                }
                catch (Exception)
                {
                    throw new AudioDriverException("AsioDriver could not be initialized.");
                }
            }
        }

        public override void Initialize(int bufferSize, int sampleRate)
        {
        }

        void SoundEngineAdapter_OnSampleRateChanged(double sampleRate)
        {
            AudioInput.SampleRate = sampleRate;
            AudioOutput.SampleRate = sampleRate;

            RaiseOnSampleRateChanged(sampleRate);
        }


        void SoundEngineAdapter_OnBufferSizeChanged(int min, int max, int preferred)
        {
            foreach (var channel in AudioInput.Channels)
            {
                channel.SetBufferSize(preferred);
            }
            foreach (var channel in AudioOutput.Channels)
            {
                channel.SetBufferSize(preferred);
            }

            RaiseOnBufferSizeChanged(min, max, preferred);
        }

        bool bufferUpdating;
        bool requiredClose;

        unsafe void driver_BufferUpdate(object sender, EventArgs e)
        {
            if (requiredClose)
            {
                bufferUpdating = false;
                return;
            }
            bufferUpdating = true;

            // the driver is the sender
            AsioDriver driver = sender as AsioDriver;

            // Check change of input channels.
            if (m_currentDriver.NumberInputChannels != m_last_input_channels)
            {
                // No messing, this is high priority stuff.
                Thread.CurrentThread.Priority = ThreadPriority.Highest;

                m_last_input_channels = m_currentDriver.NumberInputChannels;

                // Set input channels.
                AudioInput.SetChannels(m_last_input_channels, m_currentDriver.BufferSizex.PreferredSize);

                ClearInputs();

                // Copy input channels.
                for (int i = m_currentDriver.NumberInputChannels - 1; i >= 0; i--)
                {
                    string name = m_currentDriver.InputChannels[i].Name;

                    // Set input channel name.
                    AudioInput.Channels[i].Name = name;

                    // Add audio input class.
                    AudioInput audioInput = new AudioInput(this, AudioInput.Channels[i], name);
                    AddInput(audioInput);
                }

                RaiseOnInputChannelChanged(m_last_input_channels);
            }


            // Check change of output channels.
            if (m_currentDriver.NumberOutputChannels != m_last_output_channels)
            {
                // No messing, this is high priority stuff.
                Thread.CurrentThread.Priority = ThreadPriority.Highest;

                m_last_output_channels = m_currentDriver.NumberOutputChannels;

                AudioOutput.SetChannels(m_last_output_channels, m_currentDriver.BufferSizex.PreferredSize);

                for (int i = AudioOutput.Channels.Length - 1; i >= 0; i--)
                {
                    // Set output channel name.
                    AudioOutput.Channels[i].Name = m_currentDriver.OutputChannels[i].Name;
                }

                RaiseOnOutputChannelChanged(m_last_output_channels);
            }

            // Check change of buffer size.
            if (m_currentDriver.BufferSizex.PreferredSize != m_last_buffer_size)
            {
                // No messing, this is high priority stuff.
                Thread.CurrentThread.Priority = ThreadPriority.Highest;

                m_last_buffer_size = m_currentDriver.BufferSizex.PreferredSize;
                m_last_min_buffer_size = m_currentDriver.BufferSizex.MinSize;
                m_last_max_buffer_size = m_currentDriver.BufferSizex.MaxSize;

                // Never check if null. This class will always connect this event.
                SoundEngineAdapter_OnBufferSizeChanged(m_currentDriver.BufferSizex.MinSize, m_currentDriver.BufferSizex.MaxSize, m_currentDriver.BufferSizex.PreferredSize);
            }

            // Update the state.
            if (m_last_sampleRate != m_currentDriver.SampleRate)
            {
                // No messing, this is high priority stuff.
                Thread.CurrentThread.Priority = ThreadPriority.Highest;

                m_last_sampleRate = m_currentDriver.SampleRate;
                AudioInput.SampleRate = m_currentDriver.SampleRate;
                AudioOutput.SampleRate = m_currentDriver.SampleRate;
                SoundEngineAdapter_OnSampleRateChanged(m_currentDriver.SampleRate);
            }

            // Clear output audio.
            foreach (var channel in AudioOutput.Channels)
            {
                Array.Clear(channel.Samples, 0, channel.Samples.Length);
            }
            // Copy input audio.
            for (int i = driver.NumberInputChannels - 1; i >= 0; i--)
            {
                // Copy input audio to soundengine3 channel.
                fixed (double* inputArray = AudioInput.Channels[i].Samples)
                {
                    BlueWave.Interop.Asio.Channel inputChannel = driver.InputChannels[i];
                    for (int j = driver.InputChannels[i].BufferSize - 1; j >= 0; j--)
                    {
                        inputArray[j] = inputChannel[j];
                    }
                }
            }

            // Trigger the on buffer event.
            RaiseOnBufferUpdate(AudioOutput);

            // Copy audio to the speakers.
            for (int i = driver.NumberOutputChannels - 1; i >= 0; i--)
            {
                BlueWave.Interop.Asio.Channel outputChannel = driver.OutputChannels[i];
                fixed (double* completeOutputArray = AudioOutput.Channels[i].Samples)
                {
                    for (int j = AudioOutput.Channels[i].Samples.Length - 1; j >= 0; j--)
                    {
                        outputChannel[j] = (float)completeOutputArray[j];
                    }
                }
			}

            bufferUpdating = false;
        }
        
        public override void Start()
        {
            m_currentDriver.Start();
            requiredClose = false;
            mStarted = true;
        }

        public override void Stop()
        {
            requiredClose = true;

            while (bufferUpdating)
            {

            }

            m_currentDriver.Stop();
            // TODO: välja detta drivrutin hela tiden tills det låser sig. Då kommer vi fasna i Process-methoden när den förstöker fråna 
            //       ASIO efter buffer size direkt via property. Fixa så att vi har sparat ner den till en variabel. Tror allt blir någon form av deadlock i ASIO.

            mStarted = false;
        }

        public override void Dispose()
        {
            // Release ASIO.
            if (m_currentDriver != null)
            {
                m_currentDriver.Release();
            }
        }

        public override void GetGUIControls(List<ParameterProperties> uiControls)
        {
            ButtonParameter asioSettings = new ButtonParameter(null, false);
            asioSettings.Toggle = false;
            asioSettings.ButtonText = "Open ASIO settings";
            asioSettings.OnButtonValueChanged += delegate(bool state)
            {
                m_currentDriver.ShowControlPanel();
            };
            uiControls.Add(asioSettings);
        }
    }
}
